package com.amkai.aes.webservice.binary.objecthandler.inventory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import com.amkai.aes.aescommon.diee.results.DIEEContentMessage;
import com.amkai.aes.aescommon.diee.serverservices.ElectronicPurchaseOrderServiceResult;
import com.amkai.aes.aescommon.exceptions.AOException;
import com.amkai.aes.aescommon.factory.RADBeanFactory;
import com.amkai.aes.aescommon.service.binary.job.inventory.epo.ElectronicPurchaseOrderJob;
import com.amkai.aes.aescommon.service.binary.job.inventory.epo.ElectronicPurchaseOrderResult;
import com.amkai.aes.aescommon.service.diee.DIEECommonUtils;
import com.amkai.aes.entities.inventory.epo.ElectronicPurchaseOrder_Entity;
import com.amkai.aes.entities.inventory.purchasing.PurchaseOrder_Entity;
import com.amkai.aes.generatedbeans.inventory.epo.ElectronicPurchaseOrder;
import com.amkai.aes.generatedbeans.inventory.epo.ElectronicPurchaseOrderResponse;
import com.amkai.aes.generatedbeans.inventory.epo.ElectronicPurchaseOrderStatus;
import com.amkai.aes.generatedbeans.inventory.epo.ElectronicPurchaseOrderType;
import com.amkai.aes.generatedbeans.inventory.epo.ElectronicPurchaseOrderVendor;
import com.amkai.aes.generatedbeans.inventory.purchasing.POStatusKind;
import com.amkai.aes.generatedbeans.inventory.purchasing.PurchaseOrder;
import com.amkai.aes.generatedbeans.organization.Department;
import com.amkai.aes.generatedbeans.passport.Passport;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;
import com.amkai.diee.extractor.DataExtractor;
import com.amkai.diee.utils.Utils;
import com.amkai.extensions.server.services.IElectronicPurchaseOrderManager;
import com.amkai.extensions.server.services.IElectronicPurchaseOrderProvider;
import com.amkai.extensions.server.services.ServerServiceManager;
import com.amkai.rad.designerbase.beans.ChangedKind;

public class ElectronicPurchaseOrderHandler implements JobObjectHandler<ElectronicPurchaseOrderJob, Serializable, ElectronicPurchaseOrderResult>, Comparator<Object[]> {

	private final static int MAX_RECORD_SIZE = 1000;
	
	@Override
	public ElectronicPurchaseOrderResult process(ElectronicPurchaseOrderJob job, Serializable object) throws Exception {
		ElectronicPurchaseOrderResult result = new ElectronicPurchaseOrderResult();
		switch (job.getJobKind()) {
		case CHECK_MODULE_AVAILABLE:
			checkModuleAvailable(result, false);
			break;
		case LOAD_VENDORS:
			loadVendors(job, result);
			break;
		case LOAD_PURCHASE_ORDERS:
			loadPurchaseOrders(job, result);
			break;
		case VALIDATE:
			validate(job, result);
			break;
		case SEND_PO:
			sendPurchaseOrder(job, result);
			break;
		case LOAD_ELECTRONIC_PURCHASE_ORDERS:
			loadElectronicPurchaseOrders(job, result);
			break;
		case LOAD_ELECTRONIC_PURCHASE_ORDER:
			loadElectronicPurchaseOrder(job, result);
			break;
		case REMOVE_OLD_ENTRIES:
			removeOldEntries(job, result);
			break;
		}
		return result;
	}

	private final void checkModuleAvailable(ElectronicPurchaseOrderResult result, boolean throwException) throws Exception {
		IElectronicPurchaseOrderManager manager = ServerServiceManager.getInstance().getElectronicPurchaseOrderManager();
		// AES-17445
		result.setModuleAvailable(manager!=null && manager.isInitialized());
		if (throwException && !result.isModuleAvailable()) {
			throw new AOException("Electronic purchase order manager is not started yet");
		}
	}
	
	@Override
	public int compare(Object[] o1, Object[] o2) {
		return DIEECommonUtils.compare((String)o1[1], (String)o2[1]);
	}

	private final void loadVendors(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		ArrayList<Object[]> list = new ArrayList<Object[]>();
		if (job.getEpoType()==null || job.getEpoType().getId()==null) {
			List<Object[]> q = sm.execute(
				"select v.id, v.description from Organization as v " +
				"where v.typeWrapper.id = 0 and v.baseOrganization.id = " + job.getModuleDepartment().getId()
			).list();
			if (q!=null)
				list.addAll(q);
		} else {
			ElectronicPurchaseOrderType epoType = sm.get(ElectronicPurchaseOrderType.class, job.getEpoType().getId());
			for (ElectronicPurchaseOrderVendor vendor : epoType.getElectronicPurchaseOrderVendors()) {
				list.add(new Object[] {vendor.getOrganization().getId(), vendor.getOrganization().getDescription()});
			}
		}
		Collections.sort(list, this);
		list.trimToSize();
		result.setTableData(list);
	}

	private final boolean isApproveNeeded(SessionManager sm, Department department) {
		Object approverId = sm.execute(
				"select iso.approvalBy.id from InventorySetupOptions as iso where iso.baseOrganization.id = " + department.getId()
			).uniqueResult();
		return (approverId!=null);
	}

	private final void loadElectronicPurchaseOrders(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		DataExtractor<ElectronicPurchaseOrder, ElectronicPurchaseOrder_Entity<ElectronicPurchaseOrder>> extractor = new DataExtractor<ElectronicPurchaseOrder, ElectronicPurchaseOrder_Entity<ElectronicPurchaseOrder>>(ElectronicPurchaseOrder.class, sm);
		ElectronicPurchaseOrder_Entity<ElectronicPurchaseOrder> entity = extractor.getEntity();
		// BE
		extractor.addConditionRule(entity._purchaseOrder()._baseOrganization()._$equals(job.getModuleDepartment()));
		// EPO type
		if (job.getEpoType()!=null) {
			extractor.addConditionRule(entity._electronicPurchaseOrderType()._id()._$equals(job.getEpoType().getId()));
		}
		// vendors
		if (job.getVendorId()!=null) {
			extractor.addConditionRule(entity._purchaseOrder()._vendor()._id()._$equals(job.getVendorId()));
		}
		// EPO date
		if (job.getStartDate()!=null) {
			extractor.addConditionRule(entity._created()._$from_date(job.getStartDate()));
		}
		if (job.getEndDate()!=null) {
			extractor.addConditionRule(entity._created()._$through_date(job.getEndDate()));
		}
		// ordering
		extractor.addOrderingRule(entity._created(), false);
		// fields
		extractor.addDataRule(entity._purchaseOrder()._ext_info()._fullPONumber());
		extractor.addDataRule(entity._purchaseOrder()._vendor()._description());
		extractor.addDataRule(entity._created());
		extractor.addDataRule(entity._passport()._person()._displayName());
		extractor.addDataRule(entity._lastStatus());
		// query
		extractor.setFieldLevelQueryAllowed(true);
		extractor.setFieldLevelQueryForced(true);
		extractor.setMaxResultSize(MAX_RECORD_SIZE);
		extractor.setDataTruncationEnabled(true);
		extractor.setRules(extractor.getRules());
		result.setTableData(extractor.getData().toObjectList());
		if (result.getTableData().size()==MAX_RECORD_SIZE)
			result.setTableDataTruncatedSize(MAX_RECORD_SIZE);
	}
	
	private final void loadElectronicPurchaseOrder(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		if (job.getElectronicPurchaseOrderId()!=null) {
			SessionManager sm = SiteManager.Start();
			ElectronicPurchaseOrder epo = sm.get(ElectronicPurchaseOrder.class, job.getElectronicPurchaseOrderId());
			result.setElectronicPurchaseOrder(epo);
			if (epo.getPurchaseOrder()!=null) {
				epo.getPurchaseOrder().getVendor().getDescription();
			}
			if (epo.getPassport()!=null && epo.getPassport().getPerson()!=null)
				epo.getPassport().getPerson().getDisplayName();
			for (ElectronicPurchaseOrderResponse epor : epo.getElectronicPurchaseOrderResponses()) {
				// do not send response content to client
				epor.setContent(null);
			}
			if (epo.getElectronicPurchaseOrderType()!=null) {
				IElectronicPurchaseOrderProvider provider = getProvider(job, sm, epo.getElectronicPurchaseOrderType());
				if (provider!=null) {
					epo.putTranzientProperty(ElectronicPurchaseOrderJob.KEY_EPO_FUNCTION_NAMES, provider.getEPOFunctionNames());
				}
			}
		}
	}

	private final void loadPurchaseOrders(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		DataExtractor<PurchaseOrder, PurchaseOrder_Entity<PurchaseOrder>> extractor = new DataExtractor<PurchaseOrder, PurchaseOrder_Entity<PurchaseOrder>>(PurchaseOrder.class, sm);
		PurchaseOrder_Entity<PurchaseOrder> entity = extractor.getEntity();
		// BE
		extractor.addConditionRule(entity._baseOrganization()._$equals(job.getModuleDepartment()));
		// vendors
		if (job.getVendorId()!=null) {
			extractor.addConditionRule(entity._vendor()._id()._$equals(job.getVendorId()));
		} else {
			ElectronicPurchaseOrderType epoType = sm.get(ElectronicPurchaseOrderType.class, job.getEpoType().getId());
			if (epoType.getElectronicPurchaseOrderVendors().isEmpty()) {
				// do not display any PO
				extractor.addConditionRule(entity._vendor()._id()._$empty());
			} else {
				Long[] ids = new Long[epoType.getElectronicPurchaseOrderVendors().size()];
				int index = 0;
				for (ElectronicPurchaseOrderVendor vendor : epoType.getElectronicPurchaseOrderVendors()) {
					ids[index] = vendor.getOrganization().getId();
					index++;
				}
				extractor.addConditionRule(entity._vendor()._id()._$equals(ids));
			}
		}
		// status
		if (job.getStatusKinds()==null || job.getStatusKinds().length==0) {
			extractor.addConditionRule(entity._status()._$equals(POStatusKind.PENDING, POStatusKind.OPEN));
		} else {
			extractor.addConditionRule(entity._status()._$equals(job.getStatusKinds()));
		}
		// approved
		if (isApproveNeeded(sm, job.getModuleDepartment())) {
			extractor.addConditionRule(entity._approvedBy()._$not_empty());
		}
		// ordering
		extractor.addOrderingRule(entity._ext_info()._fullPONumberForSorting());
		// fields
		extractor.addDataRule(entity._ext_info()._fullPONumber());
		extractor.addDataRule(entity._poDate());
		extractor.addDataRule(entity._vendor()._description());
		extractor.addDataRule(entity._status());
		// query
		extractor.setFieldLevelQueryAllowed(true);
		extractor.setFieldLevelQueryForced(true);
		extractor.setMaxResultSize(MAX_RECORD_SIZE);
		extractor.setDataTruncationEnabled(true);
		extractor.setRules(extractor.getRules());
		result.setTableData(appendEPODates(sm, extractor.getData().toObjectList()));
		if (result.getTableData().size()==MAX_RECORD_SIZE)
			result.setTableDataTruncatedSize(MAX_RECORD_SIZE);
	}
	
	private final List<Object[]> appendEPODates(SessionManager sm, List<Object[]> poList) {
		if (poList!=null) {
			for (int i=0; i<poList.size(); i++) {
				Object[] row = poList.get(i);
				Date created = (Date) sm.execute(
						"select max(epo.created) from ElectronicPurchaseOrder as epo " +
						"inner join epo.purchaseOrder as po " +
						"where po.id = :poid").setLong("poid", (Long)row[0]).uniqueResult();
				row = Arrays.copyOf(row, row.length+1);
				row[row.length-1] = created;
				poList.set(i, row);
			}
		}
		return poList;
	}
	
	private final IElectronicPurchaseOrderProvider getProvider(ElectronicPurchaseOrderJob job, SessionManager sm, ElectronicPurchaseOrderType epoType) throws Exception {
		IElectronicPurchaseOrderManager manager = ServerServiceManager.getInstance().getElectronicPurchaseOrderManager();
		if (manager!=null) {
			if (epoType==null)
				epoType = sm.get(ElectronicPurchaseOrderType.class, job.getEpoType().getId());
			Passport passport = (job.getPassportId()==null ? null : sm.get(Passport.class, job.getPassportId()));
			Department department = sm.get(Department.class, job.getModuleDepartment().getId());
			IElectronicPurchaseOrderProvider provider = manager.getProvider(sm, passport, department, epoType, job.isDevelopmentMode());
			return provider;
		}
		return null;
	}
	
	private final void validate(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		IElectronicPurchaseOrderProvider provider = getProvider(job, sm, null);
		if (provider!=null) {
			provider.startValidation();
			if (job.getPurchaseOrderIds()!=null) {
				for (Long id : job.getPurchaseOrderIds()) {
					provider.validate(sm.get(PurchaseOrder.class, id));
				}
			}
			result.setValidationGroups(provider.getValidationResult());
		}
	}
	
	private final void sendPurchaseOrder(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		IElectronicPurchaseOrderProvider provider = getProvider(job, sm, null);
		if (provider!=null && job.getPurchaseOrderId()!=null) {
			PurchaseOrder po = sm.get(PurchaseOrder.class, job.getPurchaseOrderId());
			if (po.getStatus()!=POStatusKind.OPEN && po.getStatus()!=POStatusKind.PENDING)
				throw new IllegalStateException("Purchase order #" + po.getPoNumber() + " should be pending or open");
			if (isApproveNeeded(sm, job.getModuleDepartment()) && po.getApprovedBy()==null)
				throw new IllegalStateException("Purchase order #" + po.getPoNumber() + " is not approved");
			ElectronicPurchaseOrderServiceResult srvResult = provider.sendPurchaseOrder(po);
			if (srvResult.getResponse()!=null) {
				srvResult.getResponse().setChangedKind(ChangedKind.CREATED);
				sm.openTransaction();
				try {
					ElectronicPurchaseOrder epo = RADBeanFactory.getInstance().createBean(ElectronicPurchaseOrder.class);
					epo.setPurchaseOrder(po);
					epo.setLastStatus(srvResult.getResponse().getStatus());
					epo.setCreated(srvResult.getResponse().getReceived());
					epo.setElectronicPurchaseOrderType(provider.getEpoType());
					epo.setEpoId(srvResult.getEpoId());
					epo.setPassport(provider.getPassport());
					srvResult.getResponse().setElectronicPurchaseOrder(epo);
					epo.getElectronicPurchaseOrderResponses().add(srvResult.getResponse());
					sm.saveRAD(epo);
					sm.saveRAD(srvResult.getResponse());
					if (po.getStatus()==POStatusKind.PENDING && isSuccess(srvResult.getResponse().getStatus())) {
						po.setStatus(POStatusKind.OPEN);
						po.setProcessed(true);
						sm.saveRAD(po);
					}
					srvResult.setPoStatus(po.getStatus());
					sm.commitTransaction();
				} catch (Exception ex) {
					sm.rollbackTransaction();
					throw ex;
				}
			}
			// do not send the content to client-side
			if (srvResult.getResponse()!=null)
				srvResult.getResponse().setContent(null);
			if (srvResult.getMessages()!=null) {
				for (int i=0; i<srvResult.getMessages().size(); i++) {
					if (srvResult.getMessages().get(i) instanceof DIEEContentMessage) {
						srvResult.getMessages().remove(i);
						i--;
					}
				}
			}
			result.setResponse(srvResult);
		}
	}
	
	private final boolean isSuccess(ElectronicPurchaseOrderStatus status) {
		return (status==ElectronicPurchaseOrderStatus.ACCEPTED_201 || status==ElectronicPurchaseOrderStatus.OK_200);
	}
	
	private final void removeOldEntries(ElectronicPurchaseOrderJob job, ElectronicPurchaseOrderResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		Calendar calendar = Calendar.getInstance(Utils.getTimeZone(job.getModuleDepartment()));
		calendar.setTimeInMillis(System.currentTimeMillis());
		calendar.add(Calendar.YEAR, -1);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		List<ElectronicPurchaseOrder> list = sm.execute(
				"select epo from ElectronicPurchaseOrder as epo " +
				"left join epo.purchaseOrder as po " +
				"where po.baseOrganization.id = " + job.getModuleDepartment().getId() +
				" and epo.created < :dateLimit"
			).setTimestamp("dateLimit", calendar.getTime()).list();
		if (list!=null) {
			int count = 0;
			for (ElectronicPurchaseOrder epo : list) {
				sm.openTransaction();
				try {
					for (ElectronicPurchaseOrderResponse r : epo.getElectronicPurchaseOrderResponses())
						sm.delete(r);
					sm.delete(epo);
					sm.commitTransaction();
					count++;
					result.setRemovedEntryCount(count);
				} catch (Exception ex) {
					sm.rollbackTransaction();
					throw ex;
				}
			}
		}
	}
	
}
